#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h> 

#include "login.h"
#include "input_judge.h"
#include "log.h"

static int is_account_exist(char *account);
static int is_user_exist(struct login_info *p_login_info);
static int encipher_or_decipher(char *data, int data_length);
static int file_lock_set(FILE *stream, int type);


/**
 * @brief   初始化登陆信息
 * @param   void
 * @return  错误返回-1 失败返回FALSE 成功返回TRUE
 */
int login_init(void)
{
    FILE *fp_login_info = NULL;
    struct login_info login_info_data;
    struct login_info encipher_data;
    char *data;
    int data_length, ret;

    if (access(LIB_DIR, F_OK) != 0)
        mkdir(LIB_DIR, 0777);

    memset(&login_info_data, '\0', sizeof(struct login_info));
    
    strcpy(login_info_data.name, "DAJIANG");
    strcpy(login_info_data.account, "admin");
    strcpy(login_info_data.password, "admin");

    if (access(LOGIN_INFO_FILE, F_OK) != 0) {
        fp_login_info = fopen(LOGIN_INFO_FILE, "ab+");
        if (!fp_login_info) {
            DEBUG("文件打开失败\n");
            return (-LOGIN_OPEN_FILE_FAIL);
        }
        file_lock_set(fp_login_info, F_WRLCK);

        fseek(fp_login_info, 0, SEEK_SET);

        /*将要写入文件的内容加密*/
        memcpy(&encipher_data, &login_info_data, sizeof(struct login_info));
        data = (char *)&encipher_data;
        data_length = sizeof(struct login_info);
        ret = encipher_or_decipher(data, data_length);
        if (ret < 0) {
            DEBUG("用户信息加密失败\n");
            return (-LOGIN_ENCIPHER_FAIL);
        }

        if (fwrite(&encipher_data, sizeof(struct login_info), 1, fp_login_info) != 1) {
            DEBUG("fwrite error");
            fclose(fp_login_info);
            return (-LOGIN_WRITE_FILE_FAIL);
        }

        file_lock_set(fp_login_info, F_UNLCK);
        fclose(fp_login_info);
    }
    
    return TRUE;
}

/**
 * @brief   用户注册（错误时打印错误信息)
 * @param   装有需要注册的账号信息
 * @return  成功返回 1，失败返回 0
 */
int user_register(struct login_info *p_login_info)
{
    int8_t ret = 0;
    FILE *fp_login_info = NULL;
    struct login_info encipher_data;
    char *data;
    int data_length;

    if (!p_login_info) {
        DEBUG("PARAM ERROR!\n");
        return (-LOGIN_PARAM_NULL);
    }
    
    if (strlen(p_login_info->name) == 0 ||
        strlen(p_login_info->account) == 0 ||
        strlen(p_login_info->password) == 0 ) {
            
        printf("输入不能为空！\n");
        return FALSE;
    }

    /*判断用户名格式*/
    ret = is_num_or_char(p_login_info->name);
    if (ret == FALSE) {
            printf("昵称格式错误，由大小写字母或数字组成！\n");
            return FALSE;
    }

    /*判断账号格式*/
    ret = is_num_or_char(p_login_info->account);
    if (ret == FALSE) {
            printf("账号格式错误，由大小写字母或数字组成！\n");
            return FALSE;
    }

    /*判断账号是否存在*/
    ret = is_account_exist(p_login_info->account);
    if (ret == TRUE) {
            printf("账号已被注册！\n");
            return FALSE;
    }

    /*判断密码格式*/
    ret = is_num_or_char(p_login_info->password);
    if (ret == FALSE) {
            printf("密码格式错误，由大小写字母或数字组成！\n");
            return FALSE;
    }

    /*保存用户的昵称、账号、密码*/
    fp_login_info = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp_login_info) {
        DEBUG("文件打开失败\n");
        return FALSE;
    }
    file_lock_set(fp_login_info, F_WRLCK);

    /*将要写入文件的内容加密*/
    memcpy(&encipher_data, p_login_info, sizeof(struct login_info));
    data = (char *)&encipher_data;
    data_length = sizeof(struct login_info);
    ret = encipher_or_decipher(data, data_length);
    if (ret < 0) {
        DEBUG("用户信息加密失败\n");
        return (-LOGIN_ENCIPHER_FAIL);
    }

    if (fwrite(&encipher_data, sizeof(struct login_info), 1, fp_login_info) != 1) {
        DEBUG("fwrite error");
        return FALSE;
    }

    file_lock_set(fp_login_info, F_UNLCK);
    fclose(fp_login_info);
    p_login_info->is_login = 1;
    printf("注册成功\n");
    LOG("用户%s进行注册", p_login_info->name);
    
    return TRUE;
}

/**
 * @brief   用户登陆，登陆失败打印对应的错误信息
 * @param   装有账号密码的结构体
 * @return  成功1，失败返回0.
 */
int user_login(struct login_info *p_login_info)
{
    int8_t ret = 0;
    FILE *fp_login_info = NULL;
    struct login_info login_info_data;
    char *data;
    int data_length;

      if (!p_login_info) {
        DEBUG("PARAM ERROR!\n");
        return (-LOGIN_PARAM_NULL);
    }
    
    /*判断账号输入格式*/
    ret = is_num_or_char(p_login_info->account);
    if (ret == FALSE) {
        printf("账号格式错误，由大小写字母或数字组成！\n");
        return FALSE;
    }

    /*判断账号是否存在*/
    ret = is_account_exist(p_login_info->account);
    if (ret == FALSE) {
        printf("账号不存在！\n");
        return FALSE;
    }

    /*判断密码格式*/
    ret = is_num_or_char(p_login_info->password);
    if (ret == FALSE) {
        printf("密码格式错误，由大小写字母或数字组成！\n");
        return FALSE;
    }

    /*判断密码是否正确*/
    ret = is_user_exist(p_login_info);
        if (ret == FALSE) {
        printf("密码错误！\n");
        return FALSE;
        }

    fp_login_info = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp_login_info) {
        DEBUG("文件打开失败\n");
        return FALSE;
    }
    file_lock_set(fp_login_info, F_WRLCK);

    while (!feof(fp_login_info)) {
        if (fread(&login_info_data, sizeof(struct login_info), 1, fp_login_info)) {

            /*将从文件中读取的内容解密*/
            data = (char *)&login_info_data;
            data_length = sizeof(struct login_info);
            ret = encipher_or_decipher(data, data_length);
            if (ret < 0) {
                DEBUG("信息解密失败\n");
                return (-LOGIN_DECIPHER_FAIL);
            }

            if ((!strcmp(login_info_data.account, p_login_info->account)) &&
                (!strcmp(login_info_data.password, p_login_info->password))) {
                strcpy(p_login_info->name, login_info_data.name);
                file_lock_set(fp_login_info, F_UNLCK);    
                fclose(fp_login_info);
                break;
            }
        }
    }

    p_login_info->is_login = 1;
    printf("登录成功!欢迎%s!!!\n", p_login_info->name);
    LOG("用户%s进行登陆", p_login_info->name);

    return TRUE;
}

/**
 * @brief   用户注销，清空传入的结构体
 * @param   装有账号密码的结构体
 * @return  成功1，失败返回0.
 */
int user_logout(struct login_info *p_login_info) 
{
    if (!p_login_info) {
        DEBUG("PARAM ERROR!\n");
        return (-LOGIN_PARAM_NULL);
    }
    
    if(p_login_info->is_login != 1)
        return FALSE;

    p_login_info->is_login = 0;
    memset(p_login_info->name, '\0', USER_NAME_LENGTH);
    memset(p_login_info->account, '\0', USER_ACCOUNT_LENGTH);
    memset(p_login_info->password, '\0', USER_PASSWORD_LENGTH);

    return TRUE;
}

/**
 * @brief   判断用户是否管理员
 * @param   p_login_info：装有用户信息的结构体指针
 * @return  错误返回-1 失败返回FALSE 成功返回TRUE
 */
int is_admin(struct login_info *p_login_info)
{
    FILE *fp_login_info = NULL;
    struct login_info login_info_data;
    char *data;
    int data_length, ret;

    if (!p_login_info) {
        DEBUG("PARAM ERROR!\n");
        return (-LOGIN_PARAM_NULL);
    }

    fp_login_info = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp_login_info) {
        DEBUG("文件打开失败\n");
        return (-LOGIN_OPEN_FILE_FAIL);
    }
    file_lock_set(fp_login_info, F_WRLCK);

    while (!feof(fp_login_info)) {
        if (fread(&login_info_data, sizeof(struct login_info), 1, fp_login_info)) {

            /*将从文件中读取的内容解密*/
            data = (char *)&login_info_data;
            data_length = sizeof(struct login_info);
            ret = encipher_or_decipher(data, data_length);
            if (ret < 0) {
                DEBUG("信息解密失败\n");
                return (-LOGIN_DECIPHER_FAIL);
            }

            if ((!strcmp(login_info_data.account, p_login_info->account)) &&
                (!strcmp(login_info_data.password, p_login_info->password))) {
                    if ((!strcmp(login_info_data.account, "admin")) &&
                        (!strcmp(login_info_data.password, "admin"))) {
                        file_lock_set(fp_login_info, F_UNLCK);
                        fclose(fp_login_info);
                        return TRUE;
                }
            }
        }
    }

    file_lock_set(fp_login_info, F_UNLCK);
    fclose(fp_login_info);

    return FALSE;
}

/**
 * @brief   判断账号是否存在
 * @param   account：装有账号的字符串指针
 * @return  错误返回-1 失败返回FALSE 成功返回TRUE
 */
int is_account_exist(char *account)
{
    FILE *fp_login_info = NULL;
    struct login_info login_info_data;
    char *data;
    int data_length, ret;

    if (!account) {
        DEBUG("PARAM ERROR!\n");
        return (-LOGIN_PARAM_NULL);
    }

    fp_login_info = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp_login_info) {
        DEBUG("文件打开失败\n");
        return (-LOGIN_OPEN_FILE_FAIL);
    }
    file_lock_set(fp_login_info, F_WRLCK);

    while (!feof(fp_login_info)) {
        if (fread(&login_info_data, sizeof(struct login_info), 1, fp_login_info)) {

            /*将从文件中读取的内容解密*/
            data = (char *)&login_info_data;
            data_length = sizeof(struct login_info);
            ret = encipher_or_decipher(data, data_length);
            if (ret < 0) {
                DEBUG("信息解密失败\n");
                return (-LOGIN_DECIPHER_FAIL);
            }

            if (!strcmp(login_info_data.account, account))
                return TRUE;
        }
    }

    file_lock_set(fp_login_info, F_UNLCK);
    fclose(fp_login_info);
    
    return FALSE;
}

/**
 * @brief   判断用户是否存在
 * @param   p_login_info：装有用户信息的结构体指针
 * @return  错误返回-1 失败返回FALSE 成功返回TRUE
 */
int is_user_exist(struct login_info *p_login_info)
{
    FILE *fp_login_info = NULL;
    struct login_info login_info_data;
    char *data;
    int data_length, ret;

    if (!p_login_info) {
        DEBUG("PARAM ERROR!\n");
        return (-LOGIN_PARAM_NULL);
    }

    fp_login_info = fopen(LOGIN_INFO_FILE, "ab+");
    if (!fp_login_info) {
        DEBUG("文件打开失败\n");
        return (-LOGIN_OPEN_FILE_FAIL);
    }
    file_lock_set(fp_login_info, F_WRLCK);

    while (!feof(fp_login_info)) {

        if (fread(&login_info_data, sizeof(struct login_info), 1, fp_login_info)) {
            
            /*将从文件中读取的内容解密*/
            data = (char *)&login_info_data;
            data_length = sizeof(struct login_info);
            ret = encipher_or_decipher(data, data_length);
            if (ret < 0) {
                DEBUG("信息解密失败\n");
                return (-LOGIN_DECIPHER_FAIL);
            }

            if ((!strcmp(login_info_data.account, p_login_info->account)) &&
                (!strcmp(login_info_data.password, p_login_info->password))) {
                file_lock_set(fp_login_info, F_UNLCK);
                fclose(fp_login_info);
                return TRUE;
            }
        }
    }

    file_lock_set(fp_login_info, F_UNLCK);
    fclose(fp_login_info);
    
    return FALSE;
}

static int encipher_or_decipher(char *data, int data_length)
{
	uint8_t count;
	
	if (!data) {
        DEBUG("PARAM ERROR!\n");
        return -1;
    }
	
    for(count = 0; count < data_length; count++) {
        if(data[count] != '\0')
			data[count]^='!';
    }

    return 0;
}

int file_lock_set(FILE *stream, int type)
{
    int fd;
    struct flock lock;

	lock.l_type = type;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;
	lock.l_pid = -1;	

    fd = fileno(stream);

	fcntl(fd,F_GETLK,&lock);

	if (lock.l_type != F_UNLCK) {
		if (lock.l_type == F_RDLCK)  
			DEBUG("Read lock already set by %d\n",lock.l_pid);

		if (lock.l_type == F_WRLCK) 
			DEBUG("Write lock already set by %d\n",lock.l_pid);					
	}

	lock.l_type = type;
    lock.l_pid = getpid();

	if ((fcntl(fd,F_SETLKW,&lock)) < 0) {
		DEBUG("Lock failed : type = %d\n",lock.l_type);
		return -1;
	}

	switch (lock.l_type) {
		case F_RDLCK:
			DEBUG("Read lock set by %d\n", getpid());
		    break;

		case F_WRLCK:
			DEBUG("write lock set by %d\n", getpid());
		    break;

		case F_UNLCK:
			DEBUG("Release lock by %d\n", getpid());
		    break;

		default:
		    break;
	}

	return 1;
}